/*
 * @file com.chuanlian.mini.pay.service.impl.PaymentServiceImpl.java
 * @date 2021/9/29
 * @author lhz
 * @copyright 马丁洛克 Corporation 2021 版权所有
 * @version 0.0.1
 */
package com.sinux.generality.provider.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sinux.generality.basesupport.utils.exception.BusinessValidateException;
import com.sinux.generality.basesupport.utils.tool.EnvironmentUtil;
import com.sinux.generality.basesupport.utils.tool.ServiceUtil;
import com.sinux.generality.provider.pay.config.WeChatPayConfigure;
import com.sinux.generality.provider.pay.constants.NumberNum;
import com.sinux.generality.provider.pay.constants.RefundStatusEnum;
import com.sinux.generality.provider.pay.entity.GptPayOrder;
import com.sinux.generality.provider.pay.entity.GptPayOrderDetail;
import com.sinux.generality.provider.pay.entity.GptPayOrderRefund;
import com.sinux.generality.provider.pay.entity.GptPayOrderRefundDetail;
import com.sinux.generality.provider.pay.entity.GptPayPrepay;
import com.sinux.generality.provider.pay.factory.enums.CostTypeEnums;
import com.sinux.generality.provider.pay.factory.enums.PayOrderTypeEnums;
import com.sinux.generality.provider.pay.factory.enums.PaySuccessCodeEnums;
import com.sinux.generality.provider.pay.factory.masterorder.CombinedPaymentPayOrder;
import com.sinux.generality.provider.pay.factory.masterorder.Context;
import com.sinux.generality.provider.pay.factory.masterorder.MembershipPayOrder;
import com.sinux.generality.provider.pay.mapper.GptPayOrderDetailMapper;
import com.sinux.generality.provider.pay.mapper.GptPayOrderMapper;
import com.sinux.generality.provider.pay.mapper.GptPayOrderRefundDetailMapper;
import com.sinux.generality.provider.pay.mapper.GptPayOrderRefundMapper;
import com.sinux.generality.provider.pay.mapper.GptPayPrepayMapper;
import com.sinux.generality.provider.pay.service.PayOrderService;
import com.sinux.generality.provider.pay.util.WxChatHttpUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.security.Signature;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * @author lhz
 * @version 0.0.1
 * @className PaymentServiceImpl
 * @describe 微信支付相关接口实现
 * @date 2021/9/29 17:35
 */
@Service
public class PayOrderServiceImpl implements PayOrderService {

    @Resource
    private CombinedPaymentPayOrder combinedPaymentPayOrder;
    @Resource
    private MembershipPayOrder membershipPayOrder;
    @Resource
    private GptPayOrderMapper gptPayOrderMapper;
    @Resource
    private GptPayOrderDetailMapper gptPayOrderDetailMapper;
    @Resource
    private GptPayPrepayMapper gptPayPrepayMapper;
    @Resource
    private GptPayOrderRefundMapper gptPayOrderRefundMapper;
    @Resource
    private GptPayOrderRefundDetailMapper gptPayOrderRefundDetailMapper;
    @Resource
    private ServiceUtil serviceUtil;


    private final static Logger logger = LoggerFactory.getLogger(PayOrderServiceImpl.class);

    private static String paySign = "%s\n%s\n%s\n%s\n";

    private static String signType = "RSA";

    private static String charSet = "UTF-8";

    private static String algorithm = "SHA256withRSA";

    /****
     * createPayOrder 创建支付订单
     * @author lhz
     * @param payOrder 支付主单
     * @param businessOrderId 业务订单id
     * @param orderType 支付订单方式：1-合单支付，2-会员费单独支付
     * @param payType 支付类型：1-微信支付
     * @return GptPayOrder
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public GptPayOrder createPayOrder(GptPayOrder payOrder, Long businessOrderId, Integer orderType,
                                      Integer payType) {
        if (orderType.equals(PayOrderTypeEnums.COMBINEDPAY.getCode())) {
            Context context = new Context(combinedPaymentPayOrder);
            payOrder = context.createPayOrder(payOrder, businessOrderId, payType);
        } else if (orderType.equals(PayOrderTypeEnums.MEMBERSHIP.getCode())) {
            Context context = new Context(membershipPayOrder);
            payOrder = context.createPayOrder(payOrder, businessOrderId, payType);
        }
        List<GptPayOrderDetail> details = payOrder.getDetails();
        this.gptPayOrderMapper.insert(payOrder);
        Long payOrderId = payOrder.getPayOrderId();
        details.stream().forEach(detail -> {
            detail.setPayOrderId(payOrderId);
            this.gptPayOrderDetailMapper.insert(detail);
        });
        return payOrder;
    }


    /**
     * createPullOrder 拉起支付
     * @param businessOrderId 业务订单id
     * @param orderType 支付订单方式：1-合单支付，2-会员费单独支付
     * @param payType     支付类型：1-微信支付
     * @return
     * @throws Exception
     */
    public GptPayOrder createPullOrder(Long businessOrderId, Integer orderType, Integer payType) throws IOException {
        //生成支付订单
        GptPayOrder gptPayOrder = handlePayOrder(businessOrderId, orderType, payType);
        if (gptPayOrder != null) {
            //查询是否存在有效期内开启的预支付jsapi订单
            LambdaQueryWrapper<GptPayPrepay> queryWrapper = new LambdaQueryWrapper<GptPayPrepay>()
                    .eq(GptPayPrepay::getPayOrderId, gptPayOrder.getPayOrderId())
                    .eq(GptPayPrepay::getState, 1).eq(GptPayPrepay::getType, 1)
                    .gt(GptPayPrepay::getTimeExpire, LocalDateTime.now());
            GptPayPrepay hospitalPayPrepay = this.gptPayPrepayMapper.selectOne(queryWrapper);
            //如果没有预支付会话则请求生成
            if (hospitalPayPrepay == null) {
                //生成新的Jsapi预支付下单
                GptPayPrepay payPrepay = new GptPayPrepay();
                payPrepay.setPayOrderId(gptPayOrder.getPayOrderId());
                //默认有效期为2小时
                LocalDateTime timeExpire = LocalDateTime.now().plusHours(2);
                payPrepay.setTimeExpire(timeExpire);
                payPrepay.setType(1);
                this.gptPayPrepayMapper.insert(payPrepay);
                String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectMapper objectMapper = new ObjectMapper();
                ObjectNode rootNode = objectMapper.createObjectNode();
                rootNode.put("mchid", WeChatPayConfigure.merchantId).put("appid", WeChatPayConfigure.appId)
                        .put("description", gptPayOrder.getDescription())
                        .put("notify_url",
                                EnvironmentUtil.isOnline() ? WeChatPayConfigure.NOTIFY_URL
                                        : WeChatPayConfigure.TEST_NOTIFY_URL)
                        .put("out_trade_no", payPrepay.getAdvanceId().toString());
                rootNode.putObject("amount").put("total",
                        gptPayOrder.getTotal().multiply(BigDecimal.valueOf(100)).intValue());
                rootNode.putObject("payer").put("openid", gptPayOrder.getPayerOpenId());
                objectMapper.writeValue(bos, rootNode);
                Map map = WxChatHttpUtil.doPostJson(url, bos.toString("UTF-8"));
                int statusCode = (int) map.get("code");
                String result = (String) map.get("result");
                if (statusCode == PaySuccessCodeEnums.JSAPI_CODE.getCode()) {
                    JSONObject jsonObject = JSONObject.parseObject(result);
                    String prepayId = (String) jsonObject.get("prepay_id");
                    payPrepay.setPrepayId(prepayId);
                    this.gptPayPrepayMapper.updateById(payPrepay);
                    gptPayOrder.setPrepayId(prepayId);
                    return gptPayOrder;
                }
                logger.info("微信支付Jsapi参数：【{}】 异常信息：", JSON.toJSONString(rootNode), result);
                throw new BusinessValidateException(500, result);
            }
            gptPayOrder.setPrepayId(hospitalPayPrepay.getPrepayId());
            return gptPayOrder;
        }
        logger.error("微信支付JSAPI下单异常信息,支付订单为null");
        throw new BusinessValidateException(500, "支付异常");
    }


    /****
     * jsApiApy 小程序调起支付数据组织签名(jsapi)
     * @author lhz
     * @param businessOrderId
     * @param orderType  创建支付订单类型 1-合单支付，2-会员费单独支付
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, Object> jsApiApy(Long businessOrderId, Integer orderType) {
        try {
            JSONObject data = new JSONObject();
            //所有jsApi支付都视为合单支付，且属于微信支付
            GptPayOrder payOrder = this.createPullOrder(businessOrderId, orderType, 1);
            String timeSmp = String.valueOf(System.currentTimeMillis());
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String pkgInfo = StringUtils.join("prepay_id=", payOrder.getPrepayId());
            data.put("appId", WeChatPayConfigure.appId);
            data.put("timeStamp", timeSmp);
            data.put("nonceStr", uuid);
            data.put("package", pkgInfo);
            data.put("signType", signType);
            data.put("payOrder", payOrder);
            data.put("paySign", this.sign(String.format(paySign, WeChatPayConfigure.appId, timeSmp, uuid, pkgInfo),
                    WxChatHttpUtil.merchantPrivateKey));

            return serviceUtil.returnSuccess(data);
        } catch (IOException e) {
            logger.error("微信支付JSAPI下单异常信息：", e);
            throw new BusinessValidateException(500, "支付异常");
        } catch (BusinessValidateException e) {
            throw e;
        } catch (Exception e) {
            logger.error("微信支付JSAPI下单异常信息：", e);
            throw new BusinessValidateException(500, "支付异常");
        }
    }


    /****
     * handlePayOrder 处理生成支付订单
     * @author lhz
     * @param businessOrderId 业务订单id
     * @param orderType 创建支付订单类型 1-合单支付，2-会员费单独支付
     * @param payType 支付类型：1-微信支付
     * @return com.chuanlian.pojo.pay.GptPayOrder
     */

    private GptPayOrder handlePayOrder(Long businessOrderId, Integer orderType, Integer payType) {
        //查询线上支付待支付同类型创建的支付订单
        LambdaQueryWrapper<GptPayOrder> eq = new LambdaQueryWrapper<GptPayOrder>()
                .eq(GptPayOrder::getBusinessOrderId, businessOrderId).eq(GptPayOrder::getState, 1)
                .eq(GptPayOrder::getPayType, 1);
        GptPayOrder gptPayOrder = Optional.ofNullable(this.gptPayOrderMapper.selectOne(eq))
                .orElse(new GptPayOrder());
        //创建支付订单
        GptPayOrder payOrder = this.createPayOrder(gptPayOrder, businessOrderId, orderType, payType);
        return payOrder;
    }


    /**
     * 私钥签名
     *
     * @param plainText
     * @param privateKey
     * @return
     * @throws Exception
     */
    private String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature privateSignature = Signature.getInstance(algorithm);
        privateSignature.initSign(privateKey);
        privateSignature.update(plainText.getBytes(charSet));
        byte[] signature = privateSignature.sign();
        return Base64.getEncoder().encodeToString(signature);
    }




    /****
     * createRefundOrder 创建退款订单
     * @author lhz
     * @param businessId 业务订单id
     * @param refundMoney 退款金额
     * @param type 子订单类型：1-会员收费
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, Object> createRefundOrder(Long businessId, BigDecimal refundMoney, Integer type) {
        GptPayOrder gptPayOrder = gptPayOrderMapper.getPayPrepayByBusinessIdId(businessId);
        LambdaQueryWrapper<GptPayOrderDetail> detailWrapper = new LambdaQueryWrapper<GptPayOrderDetail>()
                .eq(GptPayOrderDetail::getPayOrderId, gptPayOrder.getPayOrderId())
                .eq(GptPayOrderDetail::getBusinessOrderId, businessId);
        List<GptPayOrderDetail> details = this.gptPayOrderDetailMapper.selectList(detailWrapper);
        BigDecimal refundMoneyTotal = gptPayOrder.getRefundMoney();
        BigDecimal totalPrice = gptPayOrder.getTotal();
        if (gptPayOrder == null || gptPayOrder.getState() != NumberNum.INT_2) {
            throw new BusinessValidateException(502, "支付订单状态异常");
        }
        if (gptPayOrder == null || gptPayOrder.getRefundState() != NumberNum.INT_0) {
            throw new BusinessValidateException(502, "该支付订单已退款");
        }
        if (totalPrice.compareTo(refundMoneyTotal) == 0) {
            throw new BusinessValidateException(502, "该支付订单已全额退款");
        }
        GptPayOrderRefund orderRefund = new GptPayOrderRefund();
        orderRefund.setPayOrderId(gptPayOrder.getPayOrderId());
        orderRefund.setPayerRefund(refundMoney);
        orderRefund.setPayerTotal(gptPayOrder.getTotal());
        orderRefund.setAdvanceId(gptPayOrder.getSuccessPrepayId());
        orderRefund.setRefundType(gptPayOrder.getPayType());
        BigDecimal add = refundMoneyTotal.add(refundMoney);
        GptPayOrderDetail orderDetail = new GptPayOrderDetail();
        //确定支付细项的退款状态
        BigDecimal decimalAdd = details.stream().map(GptPayOrderDetail::getPrice).reduce(BigDecimal.ZERO,
                BigDecimal::add);
        if (decimalAdd.compareTo(add) == 0) {
            orderDetail.setRefundState(2);
        } else if (decimalAdd.compareTo(add) == 1) {
            orderDetail.setRefundState(1);
        }
        //更新细项的退款状态--统计需要
        this.gptPayOrderDetailMapper.update(orderDetail, detailWrapper);
        this.gptPayOrderMapper.updateById(gptPayOrder);
        this.gptPayOrderRefundMapper.insert(orderRefund);
        GptPayOrderRefundDetail refundDetail = new GptPayOrderRefundDetail();
        refundDetail.setPayRefundId(orderRefund.getPayRefundId());
        refundDetail.setBusinessOrderId(businessId);
        refundDetail.setType(type);
        refundDetail.setPrice(refundMoney);
        this.gptPayOrderRefundDetailMapper.insert(refundDetail);
        //向微信发起退款
        refundOrder(orderRefund);
        return serviceUtil.returnSuccess();
    }

    /****
     * refundOrder 向微信发起退款
     * @author lhz
     * @param gptPayOrderRefund 退款单
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    @Override
    public Map<String, Object> refundOrder(GptPayOrderRefund gptPayOrderRefund) {
        try {
            String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode rootNode = objectMapper.createObjectNode();
            //元转为分
            int refund = gptPayOrderRefund.getPayerRefund().multiply(new BigDecimal(100)).intValue();
            int total = gptPayOrderRefund.getPayerTotal().multiply(new BigDecimal(100)).intValue();
            if (StringUtils.isBlank(gptPayOrderRefund.getAdvanceId())) {
                throw new BusinessValidateException(502, "该订单异常,请联系工作人员处理");
            }
            rootNode.put("out_trade_no", gptPayOrderRefund.getAdvanceId())
                    .put("out_refund_no", gptPayOrderRefund.getPayRefundId().toString())
                    .put("notify_url", EnvironmentUtil.isOnline() ? WeChatPayConfigure.NOTIFY_URL
                            : WeChatPayConfigure.TEST_NOTIFY_URL);
            rootNode.putObject("amount").put("refund", refund).put("total", total).put("currency", "CNY");
            objectMapper.writeValue(bos, rootNode);
            Map map = WxChatHttpUtil.doPostJson(url, bos.toString("UTF-8"));
            int code = (int) map.get("code");
            String result = (String) map.get("result");
            JSONObject jsonObject = JSON.parseObject(result);
            if (code == PaySuccessCodeEnums.JSAPI_CODE.getCode()) {
                gptPayOrderRefund.setRefundStatus(1);
                String refundId = (String) jsonObject.get("refund_id");
                String userReceivedAccount = (String) jsonObject.get("user_received_account");
                String status = (String) jsonObject.get("status");
                gptPayOrderRefund.setRefundId(refundId);
                gptPayOrderRefund.setUserReceivedAccount(userReceivedAccount);
                Integer statusCode = RefundStatusEnum.getStatus(status);
                gptPayOrderRefund.setRefundStatus(statusCode);
            } else {
                gptPayOrderRefund.setRefundStatus(3);
            }
            this.gptPayOrderRefundMapper.updateById(gptPayOrderRefund);
        } catch (IOException e) {
            throw new BusinessValidateException(502, "退款异常,请联系工作人员处理");
        } catch (BusinessValidateException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessValidateException(502, "退款异常,请联系工作人员处理");
        }
        return serviceUtil.returnSuccess();
    }
    /**
     * 支付成功后处理业务
     * @param payOrder 支付订单
     * @param successTime 支付成功时间
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleBusinessState(GptPayOrder payOrder, LocalDateTime successTime) {
        //处理业务订单状态
        LambdaQueryWrapper<GptPayOrderDetail> eq = new LambdaQueryWrapper<GptPayOrderDetail>()
                .eq(GptPayOrderDetail::getPayOrderId, payOrder.getPayOrderId());
        List<GptPayOrderDetail> hospitalPayOrderDetails = this.gptPayOrderDetailMapper.selectList(eq);
        LocalDateTime now = LocalDateTime.now();
        hospitalPayOrderDetails.forEach(a -> {
            //todo 处理业务订单状态
        });
        //支付方式为微信支付需要关闭其他预支付单子
        if (payOrder.getPayType() == 1) {
            closePrepayOrder(payOrder.getPayOrderId());
        }
    }
    /****
     * closePayOrder 关单
     * @author lhz
     * @param advanceId
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    @Override
    public Map<String, Object> closePayOrder(Long advanceId) {
        GptPayPrepay hospitalPayPrepay = this.gptPayPrepayMapper.selectById(advanceId);
        if (hospitalPayPrepay != null) {
            Long payOrderId = hospitalPayPrepay.getPayOrderId();
            GptPayOrder payOrder = this.gptPayOrderMapper.selectById(payOrderId);
            String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close";
            String replace = url.replace("{out_trade_no}", advanceId.toString());
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid", WeChatPayConfigure.merchantId);
            try {
                objectMapper.writeValue(bos, rootNode);
                Map map = WxChatHttpUtil.doPostJson(replace, bos.toString("UTF-8"));
                int statusCode = (int) map.get("code");
                if ((statusCode == PaySuccessCodeEnums.CLOSE_CODE.getCode().intValue()) && (payOrder.getState() == 1)) {
                    //更新支付订单状态为已关闭
                    payOrder.setState(4);
                    gptPayOrderMapper.updateById(payOrder);
                }
                logger.info("微信支付关单成功参数：【{}】", advanceId);
            } catch (IOException e) {
                logger.error("微信支付关单参数：【{}】 异常信息：", JSON.toJSONString(rootNode), e);
            }
        }
        return serviceUtil.returnSuccess();
    }

    /****
     * closePrepayOrder 关闭其他预支付订单
     * @author lhz
     * @param payOrderId
     * @return void
     */

    public void closePrepayOrder(Long payOrderId) {
        //查询有效期类且开启的预支付订单
        LambdaQueryWrapper<GptPayPrepay> queryWrapper = new LambdaQueryWrapper<GptPayPrepay>()
                .eq(GptPayPrepay::getPayOrderId, payOrderId).eq(GptPayPrepay::getState, 1)
                .gt(GptPayPrepay::getTimeExpire, LocalDateTime.now());
        List<GptPayPrepay> payPrepays = this.gptPayPrepayMapper.selectList(queryWrapper);
        payPrepays.stream().forEach(a -> closePayOrder(a.getAdvanceId()));
    }
}